package xymt.novaway.common.oss.core;

import cn.hutool.core.io.IoUtil;
import cn.hutool.core.util.IdUtil;
import lombok.extern.slf4j.Slf4j;
import xymt.novaway.common.core.constant.Constants;
import xymt.novaway.common.core.utils.DateUtils;
import xymt.novaway.common.core.utils.StringUtils;
import xymt.novaway.common.core.utils.file.FileUtils;
import xymt.novaway.common.oss.entity.UploadResult;
import xymt.novaway.common.oss.enums.AccessPolicyType;
import xymt.novaway.common.oss.exception.OssException;
import xymt.novaway.common.oss.properties.OssProperties;

import java.io.*;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;
import java.util.function.Consumer;

/**
 * 本地文件存储客户端
 * 实现本地文件系统的文件存储功能，按年/月/日目录结构组织文件
 *
 * @author NovaWay
 */
@Slf4j
public class LocalOssClient {

    /**
     * 服务商标识
     */
    private final String configKey;

    /**
     * 配置属性
     */
    private final OssProperties properties;

    /**
     * 本地存储基础路径
     */
    private static final String BASE_PATH = "uploads";

    /**
     * 文件访问URL前缀
     */
    private static final String URL_PREFIX = "/api/files";

    /**
     * 构造方法
     *
     * @param configKey     配置键
     * @param ossProperties OSS配置属性
     */
    public LocalOssClient(String configKey, OssProperties ossProperties) {
        this.configKey = configKey;
        this.properties = ossProperties;
        
        // 确保基础存储目录存在
        try {
            Path baseDir = Paths.get(BASE_PATH);
            if (!Files.exists(baseDir)) {
                Files.createDirectories(baseDir);
                log.info("创建本地存储基础目录: {}", baseDir.toAbsolutePath());
            }
        } catch (IOException e) {
            throw new OssException("创建本地存储目录失败: " + e.getMessage());
        }
    }

    /**
     * 上传 byte[] 数据到本地存储，使用指定的后缀构造文件路径
     *
     * @param data        要上传的 byte[] 数据
     * @param suffix      文件后缀
     * @param contentType 文件内容类型
     * @return UploadResult 包含上传后的文件信息
     * @throws OssException 如果上传失败，抛出自定义异常
     */
    public UploadResult uploadSuffix(byte[] data, String suffix, String contentType) {
        try (ByteArrayInputStream inputStream = new ByteArrayInputStream(data)) {
            return uploadSuffix(inputStream, suffix, (long) data.length, contentType);
        } catch (IOException e) {
            throw new OssException("上传文件失败: " + e.getMessage());
        }
    }

    /**
     * 上传 InputStream 到本地存储，使用指定的后缀构造文件路径
     *
     * @param inputStream 要上传的输入流
     * @param suffix      文件后缀
     * @param length      输入流长度
     * @param contentType 文件内容类型
     * @return UploadResult 包含上传后的文件信息
     * @throws OssException 如果上传失败，抛出自定义异常
     */
    public UploadResult uploadSuffix(InputStream inputStream, String suffix, Long length, String contentType) {
        String relativePath = getPath(properties.getPrefix(), suffix);
        String fullPath = BASE_PATH + "/" + relativePath;
        
        try {
            // 确保目标目录存在
            Path targetPath = Paths.get(fullPath);
            Path parentDir = targetPath.getParent();
            if (parentDir != null && !Files.exists(parentDir)) {
                Files.createDirectories(parentDir);
            }
            
            // 复制文件到目标位置
            Files.copy(inputStream, targetPath, StandardCopyOption.REPLACE_EXISTING);
            
            // 生成访问URL
            String url = getUrl() + "/" + relativePath;
            String filename = targetPath.getFileName().toString();
            
            log.info("文件上传成功: {} -> {}", filename, targetPath.toAbsolutePath());
            
            return UploadResult.builder()
                    .url(url)
                    .filename(filename)
                    .build();
                    
        } catch (IOException e) {
            throw new OssException("文件上传失败: " + e.getMessage());
        }
    }

    /**
     * 上传文件到本地存储，使用指定的后缀构造文件路径
     *
     * @param file   要上传的文件
     * @param suffix 文件后缀
     * @return UploadResult 包含上传后的文件信息
     * @throws OssException 如果上传失败，抛出自定义异常
     */
    public UploadResult uploadSuffix(File file, String suffix) {
        try (FileInputStream inputStream = new FileInputStream(file)) {
            return uploadSuffix(inputStream, suffix, file.length(), FileUtils.getMimeType(suffix));
        } catch (IOException e) {
            throw new OssException("文件上传失败: " + e.getMessage());
        }
    }

    /**
     * 从本地存储下载文件到输出流
     *
     * @param key      文件相对路径
     * @param out      输出流
     * @param consumer 内容长度消费者，用于设置响应头
     */
    public void download(String key, OutputStream out, Consumer<Long> consumer) {
        try {
            // 移除URL前缀，获取相对路径
            String relativePath = removeBaseUrl(key);
            Path filePath = Paths.get(BASE_PATH, relativePath);
            
            // 安全检查：确保文件在允许的存储目录内
            if (!isPathSafe(filePath)) {
                throw new OssException("不允许访问的文件路径: " + relativePath);
            }
            
            if (!Files.exists(filePath)) {
                throw new OssException("文件不存在: " + relativePath);
            }
            
            // 设置内容长度
            if (consumer != null) {
                consumer.accept(Files.size(filePath));
            }
            
            // 复制文件到输出流
            try (InputStream inputStream = Files.newInputStream(filePath)) {
                IoUtil.copy(inputStream, out);
            }
            
        } catch (IOException e) {
            throw new OssException("文件下载失败: " + e.getMessage());
        }
    }

    /**
     * 删除本地存储中的指定文件
     *
     * @param path 文件URL路径
     */
    public void delete(String path) {
        try {
            String relativePath = removeBaseUrl(path);
            Path filePath = Paths.get(BASE_PATH, relativePath);
            
            // 安全检查
            if (!isPathSafe(filePath)) {
                throw new OssException("不允许删除的文件路径: " + relativePath);
            }
            
            if (Files.exists(filePath)) {
                Files.delete(filePath);
                log.info("文件删除成功: {}", filePath.toAbsolutePath());
            } else {
                log.warn("文件不存在，无需删除: {}", filePath.toAbsolutePath());
            }
            
        } catch (IOException e) {
            throw new OssException("文件删除失败: " + e.getMessage());
        }
    }

    /**
     * 检查路径安全性，防止目录遍历攻击
     *
     * @param targetPath 目标路径
     * @return 是否安全
     */
    private boolean isPathSafe(Path targetPath) {
        try {
            Path basePath = Paths.get(BASE_PATH).toAbsolutePath().normalize();
            Path normalizedPath = targetPath.toAbsolutePath().normalize();
            return normalizedPath.startsWith(basePath);
        } catch (Exception e) {
            return false;
        }
    }

    /**
     * 生成文件存储路径
     *
     * @param prefix 前缀
     * @param suffix 后缀
     * @return 文件路径
     */
    public String getPath(String prefix, String suffix) {
        // 生成UUID文件名
        String uuid = IdUtil.fastSimpleUUID();
        // 生成日期路径 yyyy/MM/dd
        String datePath = DateUtils.datePath();
        // 拼接路径
        String path = StringUtils.isNotEmpty(prefix) ?
                prefix + "/" + datePath + "/" + uuid : datePath + "/" + uuid;
        return path + suffix;
    }

    /**
     * 移除路径中的基础URL部分，得到相对路径
     *
     * @param path 完整的路径，包括基础URL和相对路径
     * @return 去除基础URL后的相对路径
     */
    public String removeBaseUrl(String path) {
        String baseUrl = getUrl() + "/";
        if (path.startsWith(baseUrl)) {
            return path.substring(baseUrl.length());
        }
        // 如果path不包含完整的URL，可能只是相对路径
        if (path.startsWith(URL_PREFIX + "/")) {
            return path.substring((URL_PREFIX + "/").length());
        }
        return path;
    }

    /**
     * 获取本地存储的访问URL基础路径
     *
     * @return URL基础路径
     */
    public String getUrl() {
        String domain = properties.getDomain();
        if (StringUtils.isNotEmpty(domain)) {
            // 如果domain已包含协议头，直接使用
            if (domain.startsWith(Constants.HTTP) || domain.startsWith(Constants.HTTPS)) {
                return domain + URL_PREFIX;
            } else {
                return getIsHttps() + domain + URL_PREFIX;
            }
        }
        
        // 使用endpoint作为默认域名
        String endpoint = properties.getEndpoint();
        if (StringUtils.isNotEmpty(endpoint)) {
            return getIsHttps() + endpoint + URL_PREFIX;
        }
        
        // 默认本地访问
        return getIsHttps() + "localhost:8080" + URL_PREFIX;
    }

    /**
     * 获取协议头部
     *
     * @return 协议头部
     */
    public String getIsHttps() {
        return "Y".equals(properties.getIsHttps()) ? Constants.HTTPS : Constants.HTTP;
    }

    /**
     * 检查配置是否相同
     *
     * @param properties 配置属性
     * @return 是否相同
     */
    public boolean checkPropertiesSame(OssProperties properties) {
        return this.properties.equals(properties);
    }

    /**
     * 获取配置键
     *
     * @return 配置键
     */
    public String getConfigKey() {
        return configKey;
    }

    /**
     * 获取当前桶权限类型
     *
     * @return 当前桶权限类型
     */
    public AccessPolicyType getAccessPolicy() {
        return AccessPolicyType.getByType(properties.getAccessPolicy());
    }

}